package com.klondike.common.io;

import static com.klondike.common.Utils.checkNull;
import static com.klondike.common.Utils.isEmpty;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.klondike.common.Utils;

public class IOUtils {
    private static final Pattern SAFE_FILENAME = Pattern.compile("[\"'\\\\/\\?\\*<>\\|:]");

    IOUtils() {
    }

    public static Reader getReader(InputStream in) throws IOException {
        return new InputStreamReader(in);
    }

    public static Reader getResourceAsReader(Class<?> loader, String resource) throws IOException {
        URL url = loader.getResource(resource);
        if (url == null) {
            String msg = String.format("Resource '%s' not loaded by %s", resource, loader.getName());
            throw new IOException(msg);
        }

        return getReader(url.openStream());
    }

    public static BufferedReader newBufferedReader(Reader reader) {
        return new BufferedReader(reader);
    }

    public static void closeQuietly(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException e) {
            // sssh...
        }

    }

    public static String toString(Reader reader) throws IOException {
        StringWriter sw = new StringWriter();

        copyAndClose(reader, sw);

        return sw.toString();
    }

    public static void copyAndClose(Reader reader, Writer writer) throws IOException {
        char[] buff = new char[4 * 1024];
        int read;

        while ((read = reader.read(buff)) > -1) {
            writer.write(buff, 0, read);
        }

        writer.close();
        reader.close();
    }

    public static void copyAndClose(InputStream in, OutputStream out) throws IOException {
        copy(in, out);

        out.close();
        in.close();
    }

    private static void copy(InputStream in, OutputStream out) throws IOException {
        byte[] buff = new byte[4 * 1024];
        int read;

        while ((read = in.read(buff)) > -1) {
            out.write(buff, 0, read);
        }
    }

    public static FileFilter readOnlyFileFilter() {
        return new FileFilter() {

            @Override
            public boolean accept(File pathname) {
                return pathname.canRead() && (pathname.isDirectory() || !pathname.canWrite());
            }
        };
    }

    public static FileFilter fileNameSuffixFilter(String suffixs) {
        checkNull(suffixs, "Comma separated extns are required");

        String[] list = Utils.parseCsvLine(suffixs);

        return filterByFileExtns(list);
    }

    public static FileFilter filterByFileExtns(final String[] extensions) {
        return new FileFilter() {

            @Override
            public boolean accept(File pathname) {
                if (pathname.isDirectory())
                    return true;
                String name = pathname.getName();
                for (String ext : extensions) {
                    if (name.endsWith(ext)) {
                        return true;
                    }
                }
                return false;
            }
        };
    }

    /**
     * Escape the file name to make it a valid file name. The
     * <code>filename</code> must be name of file and does not include the
     * parent directory path.
     * 
     * @param filename
     *            the file name.
     * @return the safe file name
     */
    public static String escapeFileName(String filename) {
        return SAFE_FILENAME.matcher(filename).replaceAll(" ");
    }

    /**
     * @param name
     *            of the file
     * @return file extension prefixed with dot
     */
    public static String fileExtn(String name) {
        int dot = name.lastIndexOf('.');
        String ext = "";
        if (dot > 0)
            ext = name.substring(dot);
        return ext;
    }

    public static void traverse(File fileOrDir, FileVisitor v) throws IOException {
        if (fileOrDir.isDirectory()) {
            FileFilter filter = v.getFilter();
            File[] children = filter == null ? fileOrDir.listFiles() : fileOrDir.listFiles(filter);
            if (!isEmpty(children)) {
                for (File file : children) {
                    traverse(file, v);
                }
            }
        } else {
            v.visit(fileOrDir);
        }
    }

    public static void createZip(File directory, File out) throws IOException {
        createZip(directory, out, null);
    }

    public static void createZip(File directory, File out, FileFilter filter) throws IOException {
        createZip(directory, out, filter, -1, -1);
    }

    public static void createZip(File directory, File out, FileFilter filter, int level, int method) throws IOException {
        FileOutputStream fout = new FileOutputStream(out);

        createZip(directory, fout, filter, level, method);
    }

    public static void createZip(final File directory, final OutputStream out, final FileFilter filter,
            final int level, final int method) throws IOException {
        final ZipOutputStream zout = new ZipOutputStream(out);
        if (level >= 0) {
            zout.setLevel(level);
        }
        if (method >= 0) {
            zout.setMethod(method);
        }

        final String pathPrefix = directory.getCanonicalPath();
        FileVisitor zipEntryCreator = new FileVisitor() {

            @Override
            public void visit(File fileOrDir) throws IOException {
                if (fileOrDir.isFile()) {
                    File file = fileOrDir;

                    ZipEntry entry = new ZipEntry(getEntryName(file));
                    entry.setTime(file.lastModified());

                    zout.putNextEntry(entry);

                    copyAndCloseIn(new FileInputStream(file), zout);
                    zout.closeEntry();
                }
            }

            private String getEntryName(File file) throws IOException {
                String path = file.getCanonicalPath();

                path = path.substring(pathPrefix.length());
                if (File.separatorChar == '\\') {
                    path = path.replaceAll("\\\\", "/");
                }

                return path;
            }

            @Override
            public FileFilter getFilter() {
                return filter;
            }
        };

        traverse(directory, zipEntryCreator);

        zout.close();
    }

    public static void copyAndCloseIn(InputStream in, OutputStream out) throws IOException {

        copy(in, out);

        in.close();
    }

    public static Reader asReader(String s) {
        return new StringReader(s);
    }

    public static void writeTo(String content, File file) throws IOException {
        copyAndClose(new StringReader(content), new FileWriter(file));
    }
}
